# @ GenYamlCfg.py
#
# Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
#

import os
import sys
import re
import marshal
import string
import operator as op
import ast

from datetime import date
from collections import OrderedDict
from CommonUtility import value_to_bytearray, value_to_bytes, \
      bytes_to_value, get_bits_from_bytes, set_bits_to_bytes

# Generated file copyright header
__copyright_tmp__ = """/** @file

  Platform Configuration %s File.

  Copyright (c) %4d, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

  This file is automatically generated. Please do NOT modify !!!

**/
"""


def get_copyright_header(file_type, allow_modify=False):
    file_description = {
        'yaml': 'Boot Setting',
        'dlt': 'Delta',
        'inc': 'C Binary Blob',
        'h': 'C Struct Header'
    }
    if file_type in ['yaml', 'dlt']:
        comment_char = '#'
    else:
        comment_char = ''
    lines = __copyright_tmp__.split('\n')
    if allow_modify:
        lines = [line for line in lines if 'Please do NOT modify' not in line]
    copyright_hdr = '\n'.join('%s%s' % (comment_char, line)
                              for line in lines)[:-1] + '\n'
    return copyright_hdr % (file_description[file_type], date.today().year)


def check_quote(text):
    if (text[0] == "'" and text[-1] == "'") or (text[0] == '"'
                                                and text[-1] == '"'):
        return True
    return False


def strip_quote(text):
    new_text = text.strip()
    if check_quote(new_text):
        return new_text[1:-1]
    return text


def strip_delimiter(text, delim):
    new_text = text.strip()
    if new_text:
        if new_text[0] == delim[0] and new_text[-1] == delim[-1]:
            return new_text[1:-1]
    return text


def bytes_to_bracket_str(bytes):
    return '{ %s }' % (', '.join('0x%02x' % i for i in bytes))


def array_str_to_value(val_str):
    val_str = val_str.strip()
    val_str = strip_delimiter(val_str, '{}')
    val_str = strip_quote(val_str)
    value = 0
    for each in val_str.split(',')[::-1]:
        each = each.strip()
        value = (value << 8) | int(each, 0)
    return value


def write_lines(lines, file):
    fo = open(file, "w")
    fo.write(''.join([x[0] for x in lines]))
    fo.close()


def read_lines(file):
    if not os.path.exists(file):
        test_file = os.path.basename(file)
        if os.path.exists(test_file):
            file = test_file
    fi = open(file, 'r')
    lines = fi.readlines()
    fi.close()
    return lines


def expand_file_value(path, value_str):
    result = bytearray()
    match = re.match("\\{\\s*FILE:(.+)\\}", value_str)
    if match:
        file_list = match.group(1).split(',')
        for file in file_list:
            file = file.strip()
            bin_path = os.path.join(path, file)
            result.extend(bytearray(open(bin_path, 'rb').read()))
            print('\n\n result ', result)
    return result


class ExpressionEval(ast.NodeVisitor):
    operators = {
        ast.Add:    op.add,
        ast.Sub:    op.sub,
        ast.Mult:   op.mul,
        ast.Div:    op.floordiv,
        ast.Mod:    op.mod,
        ast.Eq:     op.eq,
        ast.NotEq:  op.ne,
        ast.Gt:     op.gt,
        ast.Lt:     op.lt,
        ast.GtE:    op.ge,
        ast.LtE:    op.le,
        ast.BitXor: op.xor,
        ast.BitAnd: op.and_,
        ast.BitOr:  op.or_,
        ast.Invert: op.invert,
        ast.USub:   op.neg
    }

    def __init__(self):
        self._debug = False
        self._expression = ''
        self._namespace = {}
        self._get_variable = None

    def eval(self, expr, vars={}):
        self._expression = expr
        if type(vars) is dict:
            self._namespace = vars
            self._get_variable = None
        else:
            self._namespace = {}
            self._get_variable = vars
        node = ast.parse(self._expression, mode='eval')
        result = self.visit(node.body)
        if self._debug:
            print('EVAL [ %s ] = %s' % (expr, str(result)))
        return result

    def visit_Name(self, node):
        if self._get_variable is not None:
            return self._get_variable(node.id)
        else:
            return self._namespace[node.id]

    def visit_Num(self, node):
        return node.value

    def visit_NameConstant(self, node):
        return node.value

    def visit_BoolOp(self, node):
        result = False
        if isinstance(node.op, ast.And):
            for value in node.values:
                result = self.visit(value)
                if not result:
                    break
        elif isinstance(node.op, ast.Or):
            for value in node.values:
                result = self.visit(value)
                if result:
                    break
        return True if result else False

    def visit_UnaryOp(self, node):
        val = self.visit(node.operand)
        return ExpressionEval.operators[type(node.op)](val)

    def visit_BinOp(self, node):
        lhs = self.visit(node.left)
        rhs = self.visit(node.right)
        return ExpressionEval.operators[type(node.op)](lhs, rhs)

    def visit_Compare(self, node):
        right = self.visit(node.left)
        result = True
        for operation, comp in zip(node.ops, node.comparators):
            if not result:
                break
            left = right
            right = self.visit(comp)
            result = ExpressionEval.operators[type(operation)](left, right)
        return result

    def visit_Call(self, node):
        if node.func.id in ['ternary']:
            condition = self.visit(node.args[0])
            val_true = self.visit(node.args[1])
            val_false = self.visit(node.args[2])
            return val_true if condition else val_false
        elif node.func.id in ['offset', 'length']:
            if self._get_variable is not None:
                return self._get_variable(node.args[0].s, node.func.id)
        else:
            raise ValueError("Unsupported function: " + repr(node))

    def generic_visit(self, node):
        raise ValueError("malformed node or string: " + repr(node))


class CFG_YAML():
    TEMPLATE = 'template'
    CONFIGS = 'configs'
    VARIABLE = 'variable'
    FORMSET = 'formset'

    def __init__(self):
        self.log_line = False
        self.allow_template = False
        self.cfg_tree = None
        self.tmp_tree = None
        self.var_dict = None
        self.def_dict = {}
        self.yaml_path = ''
        self.yaml_type = 'fsp'
        self.lines = []
        self.full_lines = []
        self.index = 0
        self.re_expand = re.compile(
            r'(.+:\s+|\s*\-\s*)!expand\s+\{\s*(\w+_TMPL)\s*:\s*\[(.+)]\s*\}')
        self.re_include = re.compile(r'(.+:\s+|\s*\-\s*)!include\s+(.+)')

    @staticmethod
    def count_indent(line):
        return next((i for i, c in enumerate(line) if not c.isspace()),
                    len(line))

    @staticmethod
    def substitue_args(text, arg_dict):
        for arg in arg_dict:
            text = text.replace('$' + arg, arg_dict[arg])
        return text

    @staticmethod
    def dprint(*args):
        pass

    def process_include(self, line, insert=True):
        match = self.re_include.match(line)
        if not match:
            raise Exception("Invalid !include format '%s' !" % line.strip())

        prefix = match.group(1)
        include = match.group(2)
        if prefix.strip() == '-':
            prefix = ''
            adjust = 0
        else:
            adjust = 2

        include = strip_quote(include)
        request = CFG_YAML.count_indent(line) + adjust

        if self.log_line:
            # remove the include line itself
            del self.full_lines[-1]

        inc_path = os.path.join(self.yaml_path, include)
        if not os.path.exists(inc_path):
            # try relative path to project root
            try_path = os.path.join(os.path.dirname(os.path.realpath(__file__)
                                                    ), "../..", include)
            if os.path.exists(try_path):
                inc_path = try_path
            else:
                raise Exception("ERROR: Cannot open file '%s'." % inc_path)

        lines = read_lines(inc_path)
        current = 0
        same_line = False
        for idx, each in enumerate(lines):
            start = each.lstrip()
            if start == '' or start[0] == '#':
                continue

            if start[0] == '>':
                # append the content directly at the same line
                same_line = True

            start = idx
            current = CFG_YAML.count_indent(each)
            break

        lines = lines[start+1:] if same_line else lines[start:]
        leading = ''
        if same_line:
            request = len(prefix)
            leading = '>'

        lines = [prefix + '%s\n' % leading] + [' ' * request +
                                               i[current:] for i in lines]
        if insert:
            self.lines = lines + self.lines

        return lines

    def process_expand(self, line):
        match = self.re_expand.match(line)
        if not match:
            raise Exception("Invalid !expand format '%s' !" % line.strip())
        lines = []
        prefix = match.group(1)
        temp_name = match.group(2)
        args = match.group(3)

        if prefix.strip() == '-':
            indent = 0
        else:
            indent = 2
        lines = self.process_expand_template(temp_name, prefix, args, indent)
        self.lines = lines + self.lines

    def process_expand_template(self, temp_name, prefix, args, indent=2):
        # expand text with arg substitution
        if temp_name not in self.tmp_tree:
            raise Exception("Could not find template '%s' !" % temp_name)
        parts = args.split(',')
        parts = [i.strip() for i in parts]
        num = len(parts)
        arg_dict = dict(zip(['(%d)' % (i + 1) for i in range(num)], parts))
        str_data = self.tmp_tree[temp_name]
        text = DefTemplate(str_data).safe_substitute(self.def_dict)
        text = CFG_YAML.substitue_args(text, arg_dict)
        target = CFG_YAML.count_indent(prefix) + indent
        current = CFG_YAML.count_indent(text)
        padding = target * ' '
        if indent == 0:
            leading = []
        else:
            leading = [prefix + '\n']
        text = leading + [(padding + i + '\n')[current:]
                          for i in text.splitlines()]
        return text

    def load_file(self, yaml_file):
        self.index = 0
        self.lines = read_lines(yaml_file)

    def peek_line(self):
        if len(self.lines) == 0:
            return None
        else:
            return self.lines[0]

    def put_line(self, line):
        self.lines.insert(0, line)
        if self.log_line:
            del self.full_lines[-1]

    def get_line(self):
        if len(self.lines) == 0:
            return None
        else:
            line = self.lines.pop(0)
            if self.log_line:
                self.full_lines.append(line.rstrip())
            return line

    def get_multiple_line(self, indent):
        text = ''
        newind = indent + 1
        while True:
            line = self.peek_line()
            if line is None:
                break
            sline = line.strip()
            if sline != '':
                newind = CFG_YAML.count_indent(line)
                if newind <= indent:
                    break
            self.get_line()
            if sline != '':
                text = text + line
        return text

    def traverse_cfg_tree(self, handler):
        def _traverse_cfg_tree(root, level=0):
            # config structure
            for key in root:
                if type(root[key]) is OrderedDict:
                    level += 1
                    handler(key, root[key], level)
                    _traverse_cfg_tree(root[key], level)
                    level -= 1
        _traverse_cfg_tree(self.cfg_tree)

    def count(self):
        def _count(name, cfgs, level):
            num[0] += 1
        num = [0]
        self.traverse_cfg_tree(_count)
        return num[0]

    def parse(self, parent_name='', curr=None, level=0):
        child = None
        last_indent = None
        key = ''
        temp_chk = {}
        temp_data = []

        while True:
            line = self.get_line()
            if line is None:
                break

            curr_line = line.strip()
            if curr_line == "## DO NOT REMOVE -- YAML Mode":
                self.yaml_type = "vfr"

            if curr_line == '' or curr_line[0] == '#':
                continue

            indent = CFG_YAML.count_indent(line)
            if last_indent is None:
                last_indent = indent

            if indent != last_indent:
                # outside of current block,  put the line back to queue
                self.put_line(' ' * indent + curr_line)

            if curr_line.endswith(': >'):
                # multiline marker
                old_count = len(self.full_lines)
                line = self.get_multiple_line(indent)
                if self.log_line and not self.allow_template \
                   and '!include ' in line:
                    # expand include in template
                    new_lines = []
                    lines = line.splitlines()
                    for idx, each in enumerate(lines):
                        if '!include ' in each:
                            new_line = ''.join(self.process_include(each,
                                                                    False))
                            new_lines.append(new_line)
                        else:
                            new_lines.append(each)
                    self.full_lines = self.full_lines[:old_count] + new_lines
                curr_line = curr_line + line

            if indent > last_indent:
                # child nodes
                if child is None:
                    raise Exception('Unexpected format at line: %s'
                                    % (curr_line))

                level += 1
                self.parse(key, child, level)
                level -= 1
                line = self.peek_line()
                if line is not None:
                    curr_line = line.strip()
                    indent = CFG_YAML.count_indent(line)
                    if indent >= last_indent:
                        # consume the line
                        self.get_line()
                else:
                    # end of file
                    indent = -1

            if curr is None:
                curr = OrderedDict()

            if indent < last_indent:
                return curr

            marker1 = curr_line[0]
            start = 1 if marker1 == '-' else 0
            pos = curr_line.find(': ')
            if marker1 == '-':
                marker2 = curr_line[curr_line.find(":")]
                pos = -1
            else:
                marker2 = curr_line[-1]

            if pos > 0:
                child = None
                key = curr_line[start:pos].strip()
                if curr_line[pos + 2] == '>':
                    curr[key] = curr_line[pos + 3:]
                else:
                    # XXXX: !include / !expand
                    if '!include ' in curr_line:
                        self.process_include(line)
                    elif '!expand ' in curr_line:
                        if self.allow_template and not self.log_line:
                            self.process_expand(line)
                    else:
                        value_str = curr_line[pos + 2:].strip()
                        curr[key] = value_str
                        if self.log_line and value_str[0] == '{':
                            # expand {FILE: xxxx} format in the log line
                            if value_str[1:].rstrip().startswith('FILE:'):
                                value_bytes = expand_file_value(
                                    self.yaml_path, value_str)
                                value_str = bytes_to_bracket_str(value_bytes)
                                self.full_lines[-1] = line[
                                    :indent] + curr_line[:pos + 2] + value_str

            elif marker2 == ':':
                child = OrderedDict()
                key = curr_line[start:-1].strip()
                if key == '$ACTION':
                    # special virtual nodes, rename to ensure unique key
                    key = '$ACTION_%04X' % self.index
                    self.index += 1

                if self.yaml_type =='fsp':
                    if key in curr:
                        if key not in temp_chk:
                            # check for duplicated keys at same level
                            temp_chk[key] = 1
                        else:
                            raise Exception("Duplicated item '%s:%s' found !"
                                            % (parent_name, key))

                    curr[key] = child
                if self.yaml_type == 'vfr':
                    if key in curr.keys():
                        if type(curr[key]) == type([]):
                            temp_data = curr[key]
                        else:
                            temp_data.append(curr[key])

                        temp_data.append(child)
                        if level < 5:
                            curr[key] = temp_data
                        temp_data = []
                    else:
                        if level < 5:
                            curr[key] = child
                if self.var_dict is None and key == CFG_YAML.VARIABLE:
                    self.var_dict = child
                if self.tmp_tree is None and key == CFG_YAML.TEMPLATE:
                    self.tmp_tree = child
                    if self.var_dict:
                        for each in self.var_dict:
                            txt = self.var_dict[each]
                            if type(txt) is str:
                                self.def_dict['(%s)' % each] = txt
                if self.tmp_tree and key == CFG_YAML.CONFIGS:
                    # apply template for the main configs
                    self.allow_template = True
                if self.tmp_tree and key == CFG_YAML.FORMSET:
                    self.allow_template = True
            else:
                child = None
                # - !include cfg_opt.yaml
                if '!include ' in curr_line:
                    self.process_include(line)

        return curr

    def load_yaml(self, opt_file):
        self.var_dict = None
        self.yaml_path = os.path.dirname(opt_file)
        self.load_file(opt_file)
        yaml_tree = self.parse()
        for key in yaml_tree.keys():
            if key.lower() == "configs":
                self.yaml_type = 'fsp'
                self.tmp_tree = yaml_tree[CFG_YAML.TEMPLATE]
                self.cfg_tree = yaml_tree[CFG_YAML.CONFIGS]
                break
            else:
                self.cfg_tree = yaml_tree
                break

        if self.yaml_type == 'vfr':
            formset_found = True
            for key in yaml_tree.keys():
                if key == CFG_YAML.FORMSET:
                    self.cfg_tree = yaml_tree[CFG_YAML.FORMSET]
                    formset_found = False
                    break

            if formset_found == True:
                self.cfg_tree = yaml_tree
        elif self.yaml_type == 'fsp':
            self.tmp_tree = yaml_tree[CFG_YAML.TEMPLATE]
            self.cfg_tree = yaml_tree[CFG_YAML.CONFIGS]

        return self.cfg_tree

    def expand_yaml(self, opt_file):
        self.log_line = True
        self.load_yaml(opt_file)
        self.log_line = False
        text = '\n'.join(self.full_lines)
        self.full_lines = []
        return text


class DefTemplate(string.Template):
    idpattern = '\\([_A-Z][_A-Z0-9]*\\)|[_A-Z][_A-Z0-9]*'


class CGenYamlCfg:
    STRUCT = '$STRUCT'
    bits_width = {'b': 1, 'B': 8, 'W': 16, 'D': 32, 'Q': 64}
    builtin_option = {'$EN_DIS': [('0', 'Disable'), ('1', 'Enable')]}
    exclude_struct = ['FSP_UPD_HEADER', 'FSPT_ARCH_UPD',
                      'FSPM_ARCH_UPD', 'FSPS_ARCH_UPD',
                      'FSPI_ARCH_UPD', 'FSPT_ARCH2_UPD',
                      'FSPM_ARCH2_UPD', 'FSPS_ARCH2_UPD',
                      'GPIO_GPP_*', 'GPIO_CFG_DATA',
                      'GpioConfPad*', 'GpioPinConfig',
                      'BOOT_OPTION*', 'PLATFORMID_CFG_DATA', '\\w+_Half[01]']
    include_tag = ['GPIO_CFG_DATA']
    keyword_set = set(['name', 'type', 'option', 'help', 'length',
                       'value', 'order', 'struct', 'condition'])

    def __init__(self):
        self._mode = ''
        self._debug = False
        self._macro_dict = {}
        self.binseg_dict = {}
        self.initialize()
        self._tk = self.import_tkinter()

    def initialize(self):
        self._old_bin = None
        self._cfg_tree = {}
        self._tmp_tree = {}
        self._cfg_list = []
        self._cfg_page = {'root': {'title': '', 'child': []}}
        self._cur_page = ''
        self._main_page = ''
        self._var_dict = {}
        self._def_dict = {}
        self._yaml_path = ''
        self.yaml_type = ''
        #Added to overcome duplicate formid
        self.form_page_map = {}
        self.formset_level = 0

    @staticmethod
    def deep_convert_dict(layer):
        # convert OrderedDict to list + dict
        new_list = layer
        if isinstance(layer, OrderedDict):
            new_list = list(layer.items())
            for idx, pair in enumerate(new_list):
                new_node = CGenYamlCfg.deep_convert_dict(pair[1])
                new_list[idx] = dict({pair[0]: new_node})
        return new_list

    @staticmethod
    def deep_convert_list(layer):
        if isinstance(layer, list):
            od = OrderedDict({})
            for each in layer:
                if isinstance(each, dict):
                    key = next(iter(each))
                    od[key] = CGenYamlCfg.deep_convert_list(each[key])
            return od
        else:
            return layer

    @staticmethod
    def expand_include_files(file_path, cur_dir=''):
        if cur_dir == '':
            cur_dir = os.path.dirname(file_path)
            file_path = os.path.basename(file_path)

        input_file_path = os.path.join(cur_dir, file_path)
        file = open(input_file_path, "r")
        lines = file.readlines()
        file.close()
        new_lines = []
        for line_num, line in enumerate(lines):
            match = re.match("^!include\\s*(.+)?$", line.strip())
            if match:
                inc_path = match.group(1)
                tmp_path = os.path.join(cur_dir, inc_path)
                org_path = tmp_path
                if not os.path.exists(tmp_path):
                    cur_dir = os.path.join(os.path.dirname
                                           (os.path.realpath(__file__)
                                            ), "..", "..")
                tmp_path = os.path.join(cur_dir, inc_path)
                if not os.path.exists(tmp_path):
                    raise Exception("ERROR: Cannot open include\
                                    file '%s'." % org_path)
                else:
                    new_lines.append(('# Included from file: %s\n' % inc_path,
                                      tmp_path, 0))
                    new_lines.append(('# %s\n' % ('=' * 80), tmp_path, 0))
                    new_lines.extend(CGenYamlCfg.expand_include_files
                                     (inc_path, cur_dir))
            else:
                new_lines.append((line, input_file_path, line_num))

        return new_lines

    @staticmethod
    def format_struct_field_name(input, count=0):
        name = ''
        cap = True
        if '_' in input:
            input = input.lower()
        for each in input:
            if each == '_':
                cap = True
                continue
            elif cap:
                each = each.upper()
                cap = False
            name = name + each

        if count > 1:
            name = '%s[%d]' % (name, count)
        #
        # FSP[T/M/S]_ARCH2_UPD struct field name is Fsp[t/m/s]Arch2Upd,
        # it should change to Fsp[t/m/s]ArchUpd for code compatibility.
        #
        name = re.sub(r'(Fsp[tms]Arch)2Upd', r'\1Upd', name)
        return name

    def get_mode(self):
        return self._mode

    def set_mode(self, mode):
        self._mode = mode

    def get_last_error(self):
        return ''

    def get_variable(self, var, attr='value'):
        if var in self._var_dict:
            var = self._var_dict[var]
            return var

        item = self.locate_cfg_item(var, False)
        if item is None:
            raise ValueError("Cannot find variable '%s' !" % var)

        if item:
            if 'indx' in item:
                item = self.get_item_by_index(item['indx'])
            if attr == 'offset':
                var = item['offset']
            elif attr == 'length':
                var = item['length']
            elif attr == 'value':
                var = self.get_cfg_item_value(item)
            else:
                raise ValueError("Unsupported variable attribute '%s' !" %
                                 attr)
        return var

    def eval(self, expr):
        def _handler(pattern):
            if pattern.group(1):
                target = 1
            else:
                target = 2
            result = self.get_variable(pattern.group(target))
            if result is None:
                raise ValueError('Unknown variable $(%s) !' %
                                 pattern.group(target))
            return hex(result)

        expr_eval = ExpressionEval()
        if '$' in expr:
            # replace known variable first
            expr = re.sub(r'\$\(([_a-zA-Z][\w\.]*)\)|\$([_a-zA-Z][\w\.]*)',
                          _handler, expr)
        return expr_eval.eval(expr, self.get_variable)

    def parse_macros(self, macro_def_str):
        # ['-DABC=1', '-D', 'CFG_DEBUG=1', '-D', 'CFG_OUTDIR=Build']
        self._macro_dict = {}
        is_expression = False
        for macro in macro_def_str:
            if macro.startswith('-D'):
                is_expression = True
                if len(macro) > 2:
                    macro = macro[2:]
                else:
                    continue
            if is_expression:
                is_expression = False
                match = re.match("(\\w+)=(.+)", macro)
                if match:
                    self._macro_dict[match.group(1)] = match.group(2)
                else:
                    match = re.match("(\\w+)", macro)
                    if match:
                        self._macro_dict[match.group(1)] = ''
        if len(self._macro_dict) == 0:
            error = 1
        else:
            error = 0
            if self._debug:
                print("INFO : Macro dictionary:")
                for each in self._macro_dict:
                    print("       $(%s) = [ %s ]"
                          % (each, self._macro_dict[each]))
        return error

    def get_cfg_list(self, page_id=None):
        cfgs = []
        if page_id is None:
            # return full list
            return self._cfg_list
        else:
            if self.yaml_type == 'fsp':
                # build a new list for items under a page ID
                cfgs = [i for i in self._cfg_list if i['cname'] and
                        (i['page'] == page_id)]
            #VFR YAML Support Start
            elif self.yaml_type =='vfr':
                for cfg in self._cfg_list:
                    for i in cfg:
                        if (i['page'] == page_id):
                            cfgs.append(i)
            #VFR YAML Support End
            return cfgs

    def get_cfg_page(self):
        return self._cfg_page

    def get_cfg_item_length(self, item):
        return item['length']

    def get_cfg_item_value(self, item, array=False):
        value_str = item['value']
        length = item['length']
        return self.get_value(value_str, length, array)

    def format_value_to_str(self, value, bit_length, old_value=''):
        # value is always int
        length = (bit_length + 7) // 8
        fmt = ''
        if old_value.startswith('0x'):
            fmt = '0x'
        elif old_value and (old_value[0] in ['"', "'", '{']):
            fmt = old_value[0]
        else:
            fmt = ''

        bvalue = value_to_bytearray(value, length)
        if fmt in ['"', "'"]:
            svalue = bvalue.rstrip(b'\x00').decode()
            value_str = fmt + svalue + fmt
        elif fmt == "{":
            value_str = '{ ' + ', '.join(['0x%02x' % i for i in bvalue]) + ' }'
        elif fmt == '0x':
            hex_len = length * 2
            if len(old_value) == hex_len + 2:
                fstr = '0x%%0%dx' % hex_len
            else:
                fstr = '0x%x'
            value_str = fstr % value
        else:
            if length <= 2:
                value_str = '%d' % value
            elif length <= 8:
                value_str = '0x%x' % value
            else:
                value_str = '{ ' + ', '.join(['0x%02x' % i for i in
                                              bvalue]) + ' }'
        return value_str

    def reformat_value_str(self, value_str, bit_length, old_value=None):
        value = self.parse_value(value_str, bit_length, False)
        if old_value is None:
            old_value = value_str
        new_value = self.format_value_to_str(value, bit_length, old_value)
        return new_value

    def get_value(self, value_str, bit_length, array=True):
        value_str = value_str.strip()
        if value_str[0] == "'" and value_str[-1] == "'" or \
           value_str[0] == '"' and value_str[-1] == '"':
            value_str = value_str[1:-1]
            bvalue = bytearray(value_str.encode())
            if len(bvalue) == 0:
                bvalue = bytearray(b'\x00')
            if array:
                return bvalue
            else:
                return bytes_to_value(bvalue)
        else:
            if value_str[0] in '{':
                value_str = value_str[1:-1].strip()
            value = 0
            for each in value_str.split(',')[::-1]:
                each = each.strip()
                value = (value << 8) | int(each, 0)
            if array:
                length = (bit_length + 7) // 8
                return value_to_bytearray(value, length)
            else:
                return value

    def parse_value(self, value_str, bit_length, array=True):
        length = (bit_length + 7) // 8
        if check_quote(value_str):
            value_str = bytes_to_bracket_str(value_str[1:-1].encode())
        elif (',' in value_str) and (value_str[0] != '{'):
            value_str = '{ %s }' % value_str
        if value_str[0] == '{':
            result = expand_file_value(self._yaml_path, value_str)
            if len(result) == 0:
                bin_list = value_str[1:-1].split(',')
                value = 0
                bit_len = 0
                unit_len = 1
                for idx, element in enumerate(bin_list):
                    each = element.strip()
                    if len(each) == 0:
                        continue

                    in_bit_field = False
                    if each[0] in "'" + '"':
                        each_value = bytearray(each[1:-1], 'utf-8')
                    elif ':' in each:
                        match = re.match("^(.+):(\\d+)([b|B|W|D|Q])$", each)
                        if match is None:
                            raise SystemExit("Exception: Invald value\
list format '%s' !" % each)
                        if match.group(1) == '0' and match.group(2) == '0':
                            unit_len = CGenYamlCfg.bits_width[match.group(3)
                                                              ] // 8
                        cur_bit_len = int(match.group(2)
                                          ) * CGenYamlCfg.bits_width[
                                              match.group(3)]
                        value += ((self.eval(match.group(1)) & (
                            1 << cur_bit_len) - 1)) << bit_len
                        bit_len += cur_bit_len
                        each_value = bytearray()
                        if idx + 1 < len(bin_list):
                            in_bit_field = True
                    else:
                        try:
                            each_value = value_to_bytearray(
                                self.eval(each.strip()), unit_len)
                        except Exception:
                            raise SystemExit("Exception: Value %d cannot \
fit into %s bytes !" % (each, unit_len))

                    if not in_bit_field:
                        if bit_len > 0:
                            if bit_len % 8 != 0:
                                raise SystemExit("Exception: Invalid bit \
field alignment '%s' !" % value_str)
                            result.extend(value_to_bytes(value, bit_len // 8))
                        value = 0
                        bit_len = 0

                    result.extend(each_value)

        elif check_quote(value_str):
            result = bytearray(value_str[1:-1], 'utf-8')  # Excluding quotes
        else:
            result = value_to_bytearray(self.eval(value_str), length)

        if len(result) < length:
            result.extend(b'\x00' * (length - len(result)))
        elif len(result) > length:
            raise SystemExit("Exception: Value '%s' is too big to fit \
into %d bytes !" % (value_str, length))

        if array:
            return result
        else:
            return bytes_to_value(result)

        return result

    def get_cfg_item_options(self, item):
        tmp_list = []
        if item['type'] == "Combo":
            if item['option'] in CGenYamlCfg.builtin_option:
                for op_val, op_str in CGenYamlCfg.builtin_option[item['option'
                                                                      ]]:
                    tmp_list.append((op_val, op_str))
            else:
                if item['option'].find(';') != -1:
                    opt_list = item['option'].split(';')
                else:
                    opt_list = re.split(', ', item['option'])
                for option in opt_list:
                    option = option.strip()
                    try:
                        if option.find(':') != -1:
                            (op_val, op_str) = option.split(':')
                        else:
                            op_val = option
                            op_str = option
                    except Exception:
                        raise SystemExit("Exception: Invalid \
option format '%s' !" % option)
                    tmp_list.append((op_val, op_str))
        return tmp_list


    def get_page_title(self, page_id, top=None):
        if top is None:
            top = self.get_cfg_page()['root']
        for node in top['child']:
            page_key = next(iter(node))
            if page_id == page_key:
                return node[page_key]['title']
            else:
                result = self.get_page_title(page_id, node[page_key])
                if result is not None:
                    return result
        return None

    def print_pages(self, top=None, level=0):
        if top is None:
            top = self.get_cfg_page()['root']
        for node in top['child']:
            page_id = next(iter(node))
            print('%s%s: %s' % ('  ' * level, page_id, node[page_id]['title']))
            level += 1
            self.print_pages(node[page_id], level)
            level -= 1

    def get_item_by_index(self, index):
        return self._cfg_list[index]

    def get_item_by_path(self, path):
        node = self.locate_cfg_item(path)
        if node:
            return self.get_item_by_index(node['indx'])
        else:
            return None

    def locate_cfg_path(self, item):
        def _locate_cfg_path(root, level=0):
            # config structure
            if item is root:
                return path
            for key in root:
                if type(root[key]) is OrderedDict:
                    level += 1
                    path.append(key)
                    ret = _locate_cfg_path(root[key], level)
                    if ret:
                        return ret
                    path.pop()
            return None
        path = []
        return _locate_cfg_path(self._cfg_tree)

    def locate_cfg_item(self, path, allow_exp=True):
        def _locate_cfg_item(root, path, level=0):
            if len(path) == level:
                return root
            if type(root) == type([]):
                for temp_root in root:
                    return _locate_cfg_item(temp_root, path, level)
            next_root = root.get(path[level], None)
            if next_root is None:
                if allow_exp:
                    raise Exception('Not a valid CFG config option path: %s' %
                                    '.'.join(path[:level+1]))
                else:
                    return None
            return _locate_cfg_item(next_root, path, level + 1)

        path_nodes = path.split('.')
        return _locate_cfg_item(self._cfg_tree, path_nodes)

    def traverse_cfg_tree(self, handler, top=None):
        def _traverse_cfg_tree(root, level=0):
            # config structure
            for key in root:
                if type(root[key]) is OrderedDict:
                    level += 1
                    handler(key, root[key], level)
                    _traverse_cfg_tree(root[key], level)
                    level -= 1

        if top is None:
            top = self._cfg_tree
        _traverse_cfg_tree(top)

    def print_cfgs(self, root=None, short=True, print_level=256):
        def _print_cfgs(name, cfgs, level):

            if 'indx' in cfgs:
                act_cfg = self.get_item_by_index(cfgs['indx'])
            else:
                offset = 0
                length = 0
                value = ''
                if CGenYamlCfg.STRUCT in cfgs:
                    cfg = cfgs[CGenYamlCfg.STRUCT]
                    offset = int(cfg['offset'])
                    length = int(cfg['length'])
                    if 'value' in cfg:
                        value = cfg['value']
                if length == 0:
                    return
                act_cfg = dict({'value': value, 'offset': offset,
                                'length': length})
            value = act_cfg['value']
            bit_len = act_cfg['length']
            offset = (act_cfg['offset'] + 7) // 8
            if value != '':
                try:
                    value = self.reformat_value_str(act_cfg['value'],
                                                    act_cfg['length'])

                except Exception:
                    value = act_cfg['value']
            length = bit_len // 8
            bit_len = '(%db)' % bit_len if bit_len % 8 else '' * 4
            if level <= print_level:
                if short and len(value) > 40:
                    value = '%s ... %s' % (value[:20], value[-20:])
                print('%04X:%04X%-6s %s%s : %s' % (offset, length, bit_len,
                                                   '  ' * level, name, value))

        self.traverse_cfg_tree(_print_cfgs)

    def build_var_dict(self):
        def _build_var_dict(name, cfgs, level):
            if level <= 2:
                if CGenYamlCfg.STRUCT in cfgs:
                    struct_info = cfgs[CGenYamlCfg.STRUCT]
                    self._var_dict['_LENGTH_%s_' % name] = struct_info[
                        'length'] // 8
                    self._var_dict['_OFFSET_%s_' % name] = struct_info[
                        'offset'] // 8

        self._var_dict = {}
        self.traverse_cfg_tree(_build_var_dict)
        self._var_dict['_LENGTH_'] = self._cfg_tree[CGenYamlCfg.STRUCT][
            'length'] // 8
        return 0

    def add_cfg_page(self, child, parent, title=''):
        def _add_cfg_page(cfg_page, child, parent):
            key = next(iter(cfg_page))
            if parent == key:
                cfg_page[key]['child'].append({child: {'title': title,
                                                       'child': []}})
                return True
            else:
                result = False
                for each in cfg_page[key]['child']:
                    if _add_cfg_page(each, child, parent):
                        result = True
                        break
                return result

        return _add_cfg_page(self._cfg_page, child, parent)

    def set_cur_page(self, page_str):
        if not page_str:
            return

        if ',' in page_str:
            page_list = page_str.split(',')
        else:
            page_list = [page_str]
        for page_str in page_list:
            parts = page_str.split(':')
            if len(parts) in [1, 3]:
                page = parts[0].strip()
                if len(parts) == 3:
                    # it is a new page definition, add it into tree
                    parent = parts[1] if parts[1] else 'root'
                    parent = parent.strip()
                    if parts[2][0] == '"' and parts[2][-1] == '"':
                        parts[2] = parts[2][1:-1]

                    if not self.add_cfg_page(page, parent, parts[2]):
                        raise SystemExit("Error: Cannot find parent page \
'%s'!" % parent)
            else:
                raise SystemExit("Error: Invalid page format '%s' !"
                                 % page_str)
            self._cur_page = page

    def extend_variable(self, line):
        # replace all variables
        if line == '':
            return line
        loop = 2
        while loop > 0:
            line_after = DefTemplate(line).safe_substitute(self._def_dict)
            if line == line_after:
                break
            loop -= 1
            line = line_after
        return line_after

    def reformat_number_per_type(self, itype, value):
        if check_quote(value) or value.startswith('{'):
            return value
        parts = itype.split(',')
        if len(parts) > 3 and parts[0] == 'EditNum':
            num_fmt = parts[1].strip()
        else:
            num_fmt = ''
        if num_fmt == 'HEX' and not value.startswith('0x'):
            value = '0x%X' % int(value, 10)
        elif num_fmt == 'DEC' and value.startswith('0x'):
            value = '%d' % int(value, 16)
        return value

    def add_cfg_item(self, name, item, offset, path):

        self.set_cur_page(item.get('page', ''))

        if name != '' and name[0] == '$':
            # skip all virtual node
            return 0

        if not set(item).issubset(CGenYamlCfg.keyword_set):
            for each in list(item):
                if each not in CGenYamlCfg.keyword_set:
                    raise Exception("Invalid attribute '%s' for '%s'!" %
                                    (each, '.'.join(path)))

        length = item.get('length', 0)
        if type(length) is str:
            match = re.match("^(\\d+)([b|B|W|D|Q])([B|W|D|Q]?)\\s*$", length)
            if match:
                unit_len = CGenYamlCfg.bits_width[match.group(2)]
                length = int(match.group(1), 10) * unit_len
            else:
                try:
                    length = int(length, 0) * 8
                except Exception:
                    raise Exception("Invalid length field '%s' for '%s' !" %
                                    (length, '.'.join(path)))

                if offset % 8 > 0:
                    raise Exception("Invalid alignment for field '%s' for \
'%s' !" % (name, '.'.join(path)))
        else:
            # define is length in bytes
            length = length * 8

        if name != '' and not name.isidentifier():
            raise Exception("Invalid config name '%s' for '%s' !" %
                            (name, '.'.join(path)))

        itype = str(item.get('type', 'Reserved'))
        value = str(item.get('value', ''))
        if value:
            if not (check_quote(value) or value.startswith('{')):
                if ',' in value:
                    value = '{ %s }' % value
                else:
                    value = self.reformat_number_per_type(itype, value)

        help = str(item.get('help', ''))
        if '\n' in help:
            help = ' '.join([i.strip() for i in help.splitlines()])

        option = str(item.get('option', ''))
        if '\n' in option:
            option = ' '.join([i.strip() for i in option.splitlines()])

        # extend variables for value and condition
        condition = str(item.get('condition', ''))
        if condition:
            condition = self.extend_variable(condition)
        value = self.extend_variable(value)

        order = str(item.get('order', ''))
        if order:
            if '.' in order:
                (major, minor) = order.split('.')
                order = int(major, 16)
            else:
                order = int(order, 16)
        else:
            order = offset

        cfg_item = dict()
        cfg_item['length'] = length
        cfg_item['offset'] = offset
        cfg_item['value'] = value
        cfg_item['type'] = itype
        cfg_item['cname'] = str(name)
        cfg_item['name'] = str(item.get('name', ''))
        cfg_item['help'] = help
        cfg_item['option'] = option
        cfg_item['page'] = self._cur_page
        cfg_item['order'] = order
        cfg_item['path'] = '.'.join(path)
        cfg_item['condition'] = condition
        if 'struct' in item:
            cfg_item['struct'] = item['struct']
        self._cfg_list.append(cfg_item)

        item['indx'] = len(self._cfg_list) - 1

        # remove used info for reducing pkl size
        item.pop('option', None)
        item.pop('condition', None)
        item.pop('help', None)
        item.pop('name', None)
        item.pop('page', None)

        return length

    def build_cfg_list(self, cfg_name='', top=None, path=[],
                       info={'offset': 0}):
        if top is None:
            top = self._cfg_tree
            info.clear()
            info = {'offset': 0}

        start = info['offset']
        is_leaf = True
        for key in top:
            path.append(key)
            if type(top[key]) is OrderedDict:
                is_leaf = False
                self.build_cfg_list(key, top[key], path, info)
            path.pop()

        if is_leaf:
            length = self.add_cfg_item(cfg_name, top, info['offset'], path)
            info['offset'] += length
        elif cfg_name == '' or (cfg_name and cfg_name[0] != '$'):
            # check first element for struct
            first = next(iter(top))
            struct_str = CGenYamlCfg.STRUCT
            if first != struct_str:
                struct_node = OrderedDict({})
                top[struct_str] = struct_node
                top.move_to_end(struct_str, False)
            else:
                struct_node = top[struct_str]
            struct_node['offset'] = start
            struct_node['length'] = info['offset'] - start
            if struct_node['length'] % 8 != 0:
                raise SystemExit("Error: Bits length not aligned for %s !" %
                                 str(path))

#EDK2 VFR YAML Support start

    def build_formset_list(self, form_name='', top=None, parent_form='',path =[]):

        if self.formset_level == 1:
            self._cfg_page['root']['title'] = 'Platform'
            self._cfg_page['root']['child'].append({form_name: {'title': form_name,
                                                       'child': []}})
            self._main_page = form_name

        if top is None:
            top = self._cfg_tree
            form_name = "Formset"
            self._cfg_page['root']['title'] = 'Formset'

        is_leaf = True

        if form_name == "form" or form_name == "formid":
            self._cur_page = top["title"].split('#')[0]
            self.form_page_map[top['formid'].split('#')[0]] = self._cur_page
            for driver in self._cfg_page['root']['child']:
                if list(driver.keys())[0] == self._main_page:
                    driver[self._main_page]['child'].append({self._cur_page: {'title': self._cur_page, 'child': []}})

        if form_name == "formmap":
            self._cur_page = top["formid"].split('#')[0]
            self.form_page_map[top['FormId'].split('#')[0]] = self._cur_page
            self._cfg_page['root']['child'].append({self._cur_page: {'title': self._cur_page,
                                                       'child': []}})


        form_data = {}
        temp_data = []

        for key in top:
            if key == 'include':
                form_data['type'] = key
                form_data["page"] = self._cur_page
                continue
            if type(top[key]) is list and self.formset_level <= 3:
                self.formset_level += 1
                path.append(key)
                for data in top[key]:
                    self.build_formset_list(key, data, key, path)
                path.pop()
                self.formset_level -= 1
            elif type(top[key]) is OrderedDict and (self.formset_level <= 3):
                if parent_form != '':
                    self.formset_level += 1
                    path.append(key)
                    self.build_formset_list(key, top[key], form_name, path)
                    path.pop()
                    self.formset_level -= 1
                else:
                    self.formset_level += 1
                    path.append(key)
                    self.build_formset_list(key, top[key], key, path)
                    path.pop()
                    self.formset_level -= 1

            else:
                form_data["page"] = self._cur_page
                form_data[key] = top[key]
                form_data['path'] = ".".join(path)
                if form_name != 'form' or form_name != "formid":
                    form_data["type"] = form_name
                else:
                    form_data["type"] = " "
                count = 0
                if self._cfg_list != []:
                    for cfg_name in self._cfg_list:
                        for list_data in cfg_name:
                            if key == list_data['type']:
                                count +=1
                    if count > 1:
                        temp_data = cfg_name

        if len(temp_data) != 0 or len(form_data)!=0:
            temp_data.append(form_data)
            self._cfg_list.append(temp_data)
        return

#EDK2 VFR YAML Support End

    def get_field_value(self, top=None):
        def _get_field_value(name, cfgs, level):
            if 'indx' in cfgs:
                act_cfg = self.get_item_by_index(cfgs['indx'])
                if act_cfg['length'] == 0:
                    return
                value = self.get_value(act_cfg['value'], act_cfg['length'],
                                       False)
                set_bits_to_bytes(result, act_cfg['offset'] -
                                  struct_info['offset'], act_cfg['length'],
                                  value)

        if top is None:
            top = self._cfg_tree
        struct_info = top[CGenYamlCfg.STRUCT]
        result = bytearray((struct_info['length'] + 7) // 8)
        self.traverse_cfg_tree(_get_field_value, top)
        return result

    data_diff = ''

    def find_data_difference(self, act_val, act_cfg):
        # checks for any difference between BSF and Binary file
        config_val = ''
        if act_val != act_cfg['value']:

            if 'DEC' in act_cfg['type']:
                bsf_val = '0x%x' % int(act_val)
                if bsf_val != act_cfg['value']:
                    config_val = bsf_val
                else:
                    config_val = ''
            else:
                config_val = act_val

            available_fv1 = 'none'
            available_fv2 = 'none'

            if self.detect_fsp():
                if len(self.available_fv) >= 1:
                    if len(self.available_fv) > 1:
                        available_fv1 = self.available_fv[1]
                        if self.available_fv[2]:
                            available_fv2 = self.available_fv[2]
            else:
                available_fv1 = self.available_fv[1]
                if act_cfg['length'] == 16:
                    config_val = int(config_val, 16)
                    config_val = '0x%x' % config_val
                    act_cfg['value'] = int(
                        act_cfg['value'], 16)
                    act_cfg['value'] = '0x%x' %  \
                        act_cfg['value']

            if config_val:
                string = ('.' + act_cfg['cname'])
                if (act_cfg['path'].endswith(self.available_fv[0] + string)
                    or act_cfg['path'].endswith(available_fv1 + string)
                    or act_cfg['path'].endswith(available_fv2 + string)) \
                    and 'BsfSkip' not in act_cfg['cname'] \
                        and 'Reserved' not in act_cfg['name']:
                    if act_cfg['option'] != '':
                        if act_cfg['length'] == 8:
                            config_val = int(config_val, 16)
                            config_val = '0x%x' % config_val
                            act_cfg['value'] = int(
                                act_cfg['value'], 16)
                            act_cfg['value'] = '0x%x' %  \
                                act_cfg['value']
                        option = act_cfg['option']

                        cfg_val = ''
                        bin_val = ''
                        for i in option.split(','):
                            if act_cfg['value'] in i:
                                bin_val = i
                            elif config_val in i:
                                cfg_val = i
                        if cfg_val != '' and bin_val != '':
                            self.data_diff += '\n\nBinary:        ' \
                                + act_cfg['name'] \
                                + ': ' + bin_val.replace(' ', '') \
                                + '\nConfig file:   ' \
                                + act_cfg['name'] + ': ' \
                                + cfg_val.replace(' ', '') + '\n'
                    else:
                        self.data_diff += '\n\nBinary:        ' \
                            + act_cfg['name'] + ': ' + act_cfg['value'] \
                            + '\nConfig file:   ' + act_cfg['name'] \
                            + ': ' + config_val + '\n'

    def set_field_value(self, top, value_bytes, force=False):
        def _set_field_value(name, cfgs, level):
            if 'indx' not in cfgs:
                return
            act_cfg = self.get_item_by_index(cfgs['indx'])
            actual_offset = act_cfg['offset'] - struct_info['offset']
            if force or act_cfg['value'] == '':
                value = get_bits_from_bytes(full_bytes,
                                            actual_offset,
                                            act_cfg['length'])
                act_val = act_cfg['value']
                if act_val == '':
                    act_val = '%d' % value
                act_val = self.reformat_number_per_type(act_cfg
                                                        ['type'],
                                                        act_val)
                act_cfg['value'] = self.format_value_to_str(
                    value, act_cfg['length'], act_val)
                self.find_data_difference(act_val, act_cfg)

        if 'indx' in top:
            # it is config option
            value = bytes_to_value(value_bytes)
            act_cfg = self.get_item_by_index(top['indx'])
            act_cfg['value'] = self.format_value_to_str(
                value, act_cfg['length'], act_cfg['value'])
        else:
            # it is structure
            struct_info = top[CGenYamlCfg.STRUCT]
            length = struct_info['length'] // 8
            full_bytes = bytearray(value_bytes[:length])
            if len(full_bytes) < length:
                full_bytes.extend(bytearray(length - len(value_bytes)))
            self.traverse_cfg_tree(_set_field_value, top)

    def update_def_value(self):
        def _update_def_value(name, cfgs, level):
            if 'indx' in cfgs:
                act_cfg = self.get_item_by_index(cfgs['indx'])
                if act_cfg['value'] != '' and act_cfg['length'] > 0:
                    try:
                        act_cfg['value'] = self.reformat_value_str(
                            act_cfg['value'], act_cfg['length'])
                    except Exception:
                        raise Exception("Invalid value expression '%s' \
for '%s' !" % (act_cfg['value'], act_cfg['path']))
            else:
                if CGenYamlCfg.STRUCT in cfgs and 'value' in \
                   cfgs[CGenYamlCfg.STRUCT]:
                    curr = cfgs[CGenYamlCfg.STRUCT]
                    value_bytes = self.get_value(curr['value'],
                                                 curr['length'], True)
                    self.set_field_value(cfgs, value_bytes)

        self.traverse_cfg_tree(_update_def_value, self._cfg_tree)

    def evaluate_condition(self, item):
        expr = item['condition']
        result = self.parse_value(expr, 1, False)
        return result

    def detect_fsp(self):
        cfg_segs = self.get_cfg_segment()
        if len(cfg_segs) == 3:
            fsp = True
            for idx, seg in enumerate(cfg_segs):
                if not seg[0].endswith('UPD_%s' % 'TMSI'[idx]):
                    fsp = False
                    break
        else:
            fsp = False
        if fsp:
            self.set_mode('FSP')
        return fsp

    def get_cfg_segment(self):
        def _get_cfg_segment(name, cfgs, level):
            if 'indx' not in cfgs:
                if name.startswith('$ACTION_'):
                    if 'find' in cfgs:
                        find[0] = cfgs['find']
            else:
                if find[0]:
                    act_cfg = self.get_item_by_index(cfgs['indx'])
                    segments.append([find[0], act_cfg['offset'] // 8, 0])
                    find[0] = ''
                return

        find = ['']
        segments = []
        self.traverse_cfg_tree(_get_cfg_segment, self._cfg_tree)
        cfg_len = self._cfg_tree[CGenYamlCfg.STRUCT]['length'] // 8
        if len(segments) == 0:
            segments.append(['', 0, cfg_len])

        segments.append(['', cfg_len, 0])
        cfg_segs = []
        for idx, each in enumerate(segments[:-1]):
            cfg_segs.append((each[0], each[1],
                             segments[idx+1][1] - each[1]))

        return cfg_segs

    def import_tkinter(self):
        try:
            import tkinter
            import tkinter.messagebox
            return tkinter
        except ImportError:
            print('tkinter is not supported under current OS')
            return None

    def get_bin_segment(self, bin_data):
        cfg_segs = self.get_cfg_segment()
        bin_segs = []
        for seg in cfg_segs:
            key = seg[0].encode()
            if key == 0:
                bin_segs.append([seg[0], 0, len(bin_data)])
                break
            pos = bin_data.find(key)
            if pos >= 0:
                # ensure no other match for the key
                next_pos = bin_data.find(key, pos + len(seg[0]))
                if next_pos >= 0:
                    if key == b'$SKLFSP$' or key == b'$BSWFSP$':
                        string = ('Multiple matches for %s in '
                                  'binary!\n\nA workaround applied to such '
                                  'FSP 1.x binary to use second'
                                  ' match instead of first match!' % key)
                        if self._tk:
                            self._tk.messagebox.showwarning('Warning: ', string)
                        else:
                            print('Warning: ', string)
                        pos = next_pos
                    else:
                        print("Warning: Multiple matches for '%s' "
                              "in binary, the 1st instance will be used !"
                              % seg[0])
                bin_segs.append([seg[0], pos, seg[2]])
                self.binseg_dict[seg[0]] = pos
            else:
                bin_segs.append([seg[0], -1, seg[2]])
                self.binseg_dict[seg[0]] = -1
                continue

        return bin_segs

    available_fv = []
    missing_fv = []

    def extract_cfg_from_bin(self, bin_data):
        # get cfg bin length
        cfg_bins = bytearray()
        bin_segs = self.get_bin_segment(bin_data)
        Dummy_offset = 0
        for each in bin_segs:
            if each[1] != -1:
                cfg_bins.extend(bin_data[each[1]:each[1] + each[2]])
                self.available_fv.append(each[0])
            else:
                self.missing_fv.append(each[0])
                string = each[0] + ' is not availabe.'
                if self._tk:
                    self._tk.messagebox.showinfo('', string)
                else:
                    print('warning: ', string)
                cfg_bins.extend(bytearray(each[2]))
            Dummy_offset += each[2]
        return cfg_bins

    def save_current_to_bin(self):
        cfg_bins = self.generate_binary_array()
        if self._old_bin is None:
            return cfg_bins

        bin_data = bytearray(self._old_bin)
        bin_segs = self.get_bin_segment(self._old_bin)
        cfg_off = 0
        for each in bin_segs:
            length = each[2]
            if each[1] != -1:
                bin_data[each[1]:each[1] + length] = cfg_bins[cfg_off:
                                                              cfg_off
                                                              + length]
                cfg_off += length
            else:
                cfg_off += length

        print('Patched the loaded binary successfully !')
        return bin_data

    def show_data_difference(self, data_diff):
        if self._tk is None:
            return
        # Displays if any data difference detected in BSF and Binary file
        pop_up_text = 'There are differences in Config file and binary '\
            'data detected!\n'
        pop_up_text += data_diff

        window = self._tk.Tk()
        window.title("Data Difference")
        window.resizable(1, 1)
        # Window Size
        window.geometry("800x400")
        frame = self._tk.Frame(window, height=800, width=700)
        frame.pack(side=self._tk.BOTTOM)
        # Vertical (y) Scroll Bar
        scroll = self._tk.Scrollbar(window)
        scroll.pack(side=self._tk.RIGHT, fill=self._tk.Y)

        text = self._tk.Text(window, wrap=self._tk.NONE,
                            yscrollcommand=scroll.set,
                            width=700, height=400)
        text.insert(self._tk.INSERT, pop_up_text)
        text.pack()
        # Configure the scrollbars
        scroll.config(command=text.yview)
        exit_button = self._tk.Button(
            window, text="Close", command=window.destroy)
        exit_button.pack(in_=frame, side=self._tk.RIGHT, padx=20, pady=10)

    def load_default_from_bin(self, bin_data):
        self._old_bin = bin_data
        cfg_bins = self.extract_cfg_from_bin(bin_data)
        self.set_field_value(self._cfg_tree, cfg_bins, True)

        if self.data_diff:
            self.show_data_difference(self.data_diff)
        return cfg_bins

    def generate_binary_array(self, path=''):
        if path == '':
            top = None
        else:
            top = self.locate_cfg_item(path)
            if not top:
                raise Exception("Invalid configuration path '%s' !"
                                % path)

        return self.get_field_value(top)

    def generate_binary(self, bin_file_name, path=''):
        bin_file = open(bin_file_name, "wb")
        bin_file.write(self.generate_binary_array(path))
        bin_file.close()
        return 0

    def write_delta_file(self, out_file, platform_id, out_lines):
        dlt_fd = open(out_file, "w")
        dlt_fd.write("%s\n" % get_copyright_header('dlt', True))
        if platform_id is not None:
            dlt_fd.write('#\n')
            dlt_fd.write('# Delta configuration values for '
                         'platform ID 0x%04X\n'
                         % platform_id)
            dlt_fd.write('#\n\n')
        for line in out_lines:
            dlt_fd.write('%s\n' % line)
        dlt_fd.close()

    def override_default_value(self, dlt_file):
        error = 0
        dlt_lines = CGenYamlCfg.expand_include_files(dlt_file)

        platform_id = None
        for line, file_path, line_num in dlt_lines:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            match = re.match("\\s*([\\w\\.]+)\\s*\\|\\s*(.+)", line)
            if not match:
                raise Exception("Unrecognized line '%s' "
                                "(File:'%s' Line:%d) !"
                                % (line, file_path, line_num + 1))

            path = match.group(1)
            value_str = match.group(2)
            top = self.locate_cfg_item(path)
            if not top:
                raise Exception(
                    "Invalid configuration '%s' (File:'%s' Line:%d) !" %
                    (path, file_path, line_num + 1))

            if 'indx' in top:
                act_cfg = self.get_item_by_index(top['indx'])
                bit_len = act_cfg['length']
            else:
                struct_info = top[CGenYamlCfg.STRUCT]
                bit_len = struct_info['length']

            value_bytes = self.parse_value(value_str, bit_len)
            self.set_field_value(top, value_bytes, True)

            if path == 'PLATFORMID_CFG_DATA.PlatformId':
                platform_id = value_str

        if platform_id is None:
            raise Exception(
                "PLATFORMID_CFG_DATA.PlatformId is missing "
                "in file '%s' !" %
                (dlt_file))

        return error

    def generate_delta_file_from_bin(self, delta_file, old_data,
                                     new_data, full=False):
        new_data = self.load_default_from_bin(new_data)
        lines = []
        platform_id = None
        def_platform_id = 0

        for item in self._cfg_list:
            if not full and (item['type'] in ['Reserved']):
                continue
            old_val = get_bits_from_bytes(old_data, item['offset'],
                                          item['length'])
            new_val = get_bits_from_bytes(new_data, item['offset'],
                                          item['length'])

            full_name = item['path']
            if 'PLATFORMID_CFG_DATA.PlatformId' == full_name:
                def_platform_id = old_val
            if new_val != old_val or full:
                val_str = self.reformat_value_str(item['value'],
                                                  item['length'])
                text = '%-40s | %s' % (full_name, val_str)
                lines.append(text)

        if self.get_mode() != 'FSP':
            if platform_id is None or def_platform_id == platform_id:
                platform_id = def_platform_id
                print("WARNING: 'PlatformId' configuration is "
                      "same as default %d!" % platform_id)

            lines.insert(0, '%-40s | %s\n\n' %
                         ('PLATFORMID_CFG_DATA.PlatformId',
                          '0x%04X' % platform_id))
        else:
            platform_id = None

        self.write_delta_file(delta_file, platform_id, lines)

        return 0

    def generate_delta_file(self, delta_file, bin_file, bin_file2, full=False):
        fd = open(bin_file, 'rb')
        new_data = self.extract_cfg_from_bin(bytearray(fd.read()))
        fd.close()

        if bin_file2 == '':
            old_data = self.generate_binary_array()
        else:
            old_data = new_data
            fd = open(bin_file2, 'rb')
            new_data = self.extract_cfg_from_bin(bytearray(fd.read()))
            fd.close()

        return self.generate_delta_file_from_bin(delta_file,
                                                 old_data, new_data, full)

    def prepare_marshal(self, is_save):
        if is_save:
            # Ordered dict is not marshallable, convert to list
            self._cfg_tree = CGenYamlCfg.deep_convert_dict(self._cfg_tree)
        else:
            # Revert it back
            self._cfg_tree = CGenYamlCfg.deep_convert_list(self._cfg_tree)

    def generate_yml_file(self, in_file, out_file):
        cfg_yaml = CFG_YAML()
        text = cfg_yaml.expand_yaml(in_file)
        yml_fd = open(out_file, "w")
        yml_fd.write(text)
        yml_fd.close()
        return 0

    def write_cfg_header_file(self, hdr_file_name, tag_mode,
                              tag_dict, struct_list):
        lines = []
        lines.append ('\n')
        if self.get_mode() == 'FSP':
            lines.append('#include <FspUpd.h>\n')

        tag_mode = tag_mode & 0x7F
        tag_list = sorted(list(tag_dict.items()), key=lambda x: x[1])
        for tagname, tagval in tag_list:
            if (tag_mode == 0 and tagval >= 0x100) or \
               (tag_mode == 1 and tagval < 0x100):
                continue
            lines.append('#define    %-30s 0x%03X\n' % (
                'CDATA_%s_TAG' % tagname[:-9], tagval))
        lines.append ('\n#pragma pack(1)\n')
        name_dict = {}
        new_dict = {}
        for each in struct_list:
            if (tag_mode == 0 and each['tag'] >= 0x100) or \
               (tag_mode == 1 and each['tag'] < 0x100):
                continue
            new_dict[each['name']] = (each['alias'], each['count'], each['exclude'])
            if each['alias'] not in name_dict:
                name_dict[each['alias']] = 1
                lines.extend(self.create_struct(each['alias'],
                                                each['node'], new_dict))
        lines.append('#pragma pack()\n\n')

        self.write_header_file(lines, hdr_file_name)

    def write_header_file(self, txt_body, file_name, type='h'):
        file_name_def = os.path.basename(file_name).replace('.', '_')
        file_name_def = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', file_name_def)
        file_name_def = re.sub('([a-z0-9])([A-Z])', r'\1_\2',
                               file_name_def).upper()

        lines = []
        lines.append("%s\n" % get_copyright_header(type))
        lines.append("#ifndef __%s__\n" % file_name_def)
        lines.append ("#define __%s__\n" % file_name_def)
        lines.extend(txt_body)
        lines.append("#endif\n")

        # Don't rewrite if the contents are the same
        create = True
        if os.path.exists(file_name):
            hdr_file = open(file_name, "r")
            org_txt = hdr_file.read()
            hdr_file.close()

            new_txt = ''.join(lines)
            if org_txt == new_txt:
                create = False

        if create:
            hdr_file = open(file_name, "w")
            hdr_file.write(''.join(lines))
            hdr_file.close()

    def generate_data_inc_file(self, dat_inc_file_name, bin_file=None):
        # Put a prefix GUID before CFGDATA so that it can be located later on
        prefix = b'\xa7\xbd\x7f\x73\x20\x1e\x46\xd6\
xbe\x8f\x64\x12\x05\x8d\x0a\xa8'
        if bin_file:
            fin = open(bin_file, 'rb')
            bin_dat = prefix + bytearray(fin.read())
            fin.close()
        else:
            bin_dat = prefix + self.generate_binary_array()

        file_name = os.path.basename(dat_inc_file_name).upper()
        file_name = file_name.replace('.', '_')

        txt_lines = []

        txt_lines.append("UINT8  mConfigDataBlob[%d] = {\n" % len(bin_dat))
        count = 0
        line = ['  ']
        for each in bin_dat:
            line.append('0x%02X, ' % each)
            count = count + 1
            if (count & 0x0F) == 0:
                line.append('\n')
                txt_lines.append(''.join(line))
                line = ['  ']
        if len(line) > 1:
            txt_lines.append(''.join(line) + '\n')

        txt_lines.append("};\n\n")
        self.write_header_file(txt_lines, dat_inc_file_name, 'inc')

        return 0

    def get_struct_array_info(self, input):
        parts = input.split(':')
        if len(parts) > 1:
            var = parts[1]
            input = parts[0]
        else:
            var = ''
        array_str = input.split('[')
        name = array_str[0]
        if len(array_str) > 1:
            num_str = ''.join(c for c in array_str[-1] if c.isdigit())
            num_str = '1000' if len(num_str) == 0 else num_str
            array_num = int(num_str)
        else:
            array_num = 0
        return name, array_num, var

    def process_multilines(self, string, max_char_length):
        multilines = ''
        string_length = len(string)
        current_string_start = 0
        string_offset = 0
        break_line_dict = []
        if len(string) <= max_char_length:
            while (string_offset < string_length):
                if string_offset >= 1:
                    if string[string_offset - 1] == '\\' and string[
                       string_offset] == 'n':
                        break_line_dict.append(string_offset + 1)
                string_offset += 1
            if break_line_dict != []:
                for each in break_line_dict:
                    multilines += "  %s\n" % string[
                        current_string_start:each].lstrip()
                    current_string_start = each
                if string_length - current_string_start > 0:
                    multilines += "  %s\n" % string[
                        current_string_start:].lstrip()
            else:
                multilines = "  %s\n" % string
        else:
            new_line_start = 0
            new_line_count = 0
            found_space_char = False
            while (string_offset < string_length):
                if string_offset >= 1:
                    if new_line_count >= max_char_length - 1:
                        if string[string_offset] == ' ' and \
                           string_length - string_offset > 10:
                            break_line_dict.append(new_line_start
                                                   + new_line_count)
                            new_line_start = new_line_start + new_line_count
                            new_line_count = 0
                            found_space_char = True
                        elif string_offset == string_length - 1 and \
                                found_space_char is False:
                            break_line_dict.append(0)
                    if string[string_offset - 1] == '\\' and string[
                       string_offset] == 'n':
                        break_line_dict.append(string_offset + 1)
                        new_line_start = string_offset + 1
                        new_line_count = 0
                string_offset += 1
                new_line_count += 1
            if break_line_dict != []:
                break_line_dict.sort()
                for each in break_line_dict:
                    if each > 0:
                        multilines += "  %s\n" % string[
                            current_string_start:each].lstrip()
                    current_string_start = each
                if string_length - current_string_start > 0:
                    multilines += "  %s\n" % \
                                  string[current_string_start:].lstrip()
        return multilines

    def create_field(self, item, name, length, offset, struct,
                     bsf_name, help, option, bits_length=None):
        pos_name = 28
        name_line = ''
        help_line=''
        option_line=''

        if length == 0 and name == 'dummy':
            return '\n'

        if bits_length == 0:
            return '\n'

        is_array = False
        if length in [1, 2, 4, 8]:
            type = "UINT%d" % (length * 8)
        else:
            is_array = True
            type = "UINT8"

        if item and item['value'].startswith('{'):
            type = "UINT8"
            is_array = True

        if struct != '':
            struct_base = struct.rstrip('*')
            name = '*' * (len(struct) - len(struct_base)) + name
            struct = struct_base
            type = struct
            if struct in ['UINT8', 'UINT16', 'UINT32', 'UINT64']:
                is_array = True
                unit = int(type[4:]) // 8
                length = length / unit
            else:
                is_array = False

        if is_array:
            name = name + '[%d]' % length

        if len(type) < pos_name:
            space1 = pos_name - len(type)
        else:
            space1 = 1

        if bsf_name != '':
            name_line = " %s\n" % bsf_name
        else:
            name_line = "N/A\n"

        if help != '':
            help_line = self.process_multilines(help, 80)

        if option != '':
            option_line = self.process_multilines(option, 80)

        if offset is None:
            offset_str = '????'
        else:
            offset_str = '0x%04X' % offset

        if bits_length is None:
            bits_length = ''
        else:
            bits_length = ' : %d' % bits_length

        return "\n/** %s%s%s**/\n  %s%s%s%s;\n" % (name_line, help_line, option_line, type, ' ' * space1, name, bits_length)

    def create_struct(self, cname, top, struct_dict):
        index = 0
        last = ''
        lines = []
        off_base = -1

        if cname in struct_dict:
            if struct_dict[cname][2]:
                return []
        lines.append('\ntypedef struct {\n')
        for field in top:
            if field[0] == '$':
                continue

            index += 1

            t_item = top[field]
            if 'indx' not in t_item:
                if CGenYamlCfg.STRUCT not in top[field]:
                    continue

                if struct_dict[field][1] == 0:
                    continue

                append = True
                struct_info = top[field][CGenYamlCfg.STRUCT]

                if 'struct' in struct_info:
                    struct, array_num, var = self.get_struct_array_info(
                        struct_info['struct'])
                    if array_num > 0:
                        if last == struct:
                            append = False
                            last = struct
                        if var == '':
                            var = field

                        field = CGenYamlCfg.format_struct_field_name(
                            var, struct_dict[field][1])
                else:
                    struct = struct_dict[field][0]
                    field = CGenYamlCfg.format_struct_field_name(
                        field, struct_dict[field][1])

                if append:
                    offset = t_item['$STRUCT']['offset'] // 8
                    if off_base == -1:
                        off_base = offset
                    line = self.create_field(None, field, 0, 0, struct,
                                             '', '', '')
                    lines.append('  %s' % line)
                    last = struct
                continue

            item = self.get_item_by_index(t_item['indx'])
            if item['cname'] == 'CfgHeader' and index == 1 or \
               (item['cname'] == 'CondValue' and index == 2):
                continue

            bit_length = None
            length = (item['length'] + 7) // 8
            match = re.match("^(\\d+)([b|B|W|D|Q])([B|W|D|Q]?)",
                             t_item['length'])
            if match and match.group(2) == 'b':
                bit_length = int(match.group(1))
                if match.group(3) != '':
                    length = CGenYamlCfg.bits_width[match.group(3)] // 8
                else:
                    length = 4
            offset = item['offset'] // 8
            if off_base == -1:
                off_base = offset
            struct = item.get('struct', '')
            name = field
            prompt = item['name']
            help = item['help']
            option = item['option']
            line = self.create_field(item, name, length, offset - off_base, struct,
                                     prompt, help, option, bit_length)
            lines.append('  %s' % line)
            last = struct

        lines.append('\n} %s;\n\n' % cname)

        return lines

    def write_fsp_sig_header_file(self, hdr_file_name):
        hdr_fd = open(hdr_file_name, 'w')
        hdr_fd.write("%s\n" % get_copyright_header('h'))
        hdr_fd.write("#ifndef __FSPUPD_H__\n"
                     "#define __FSPUPD_H__\n\n"
                     "#include <FspEas.h>\n\n"
                     "#pragma pack(1)\n\n")
        lines = []
        for fsp_comp in 'TMSI':
            top = self.locate_cfg_item('FSP%s_UPD' % fsp_comp)
            if not top:
                raise Exception('Could not find FSP UPD definition !')
            bins = self.get_field_value(top)
            lines.append("#define FSP%s_UPD_SIGNATURE"
                         "    0x%016X  /* '%s' */\n\n"
                         % (fsp_comp, bytes_to_value(bins[:8]),
                            bins[:8].decode()))
        hdr_fd.write(''.join(lines))
        hdr_fd.write("#pragma pack()\n\n"
                     "#endif\n")
        hdr_fd.close()

    def create_header_file(self, hdr_file_name, com_hdr_file_name='', path=''):

        def _build_header_struct(name, cfgs, level):
            if CGenYamlCfg.STRUCT in cfgs:
                if 'CfgHeader' in cfgs:
                    # collect CFGDATA TAG IDs
                    cfghdr = self.get_item_by_index(cfgs['CfgHeader']['indx'])
                    tag_val = array_str_to_value(cfghdr['value']) >> 20
                    tag_dict[name] = tag_val
                    if level == 1:
                        tag_curr[0] = tag_val
                struct_dict[name] = (level, tag_curr[0], cfgs)
        if path == 'FSP_SIG':
            self.write_fsp_sig_header_file(hdr_file_name)
            return
        tag_curr = [0]
        tag_dict = {}
        struct_dict = {}

        if path == '':
            top = None
        else:
            top = self.locate_cfg_item(path)
            if not top:
                raise Exception("Invalid configuration path '%s' !" % path)
            _build_header_struct(path, top, 0)
        self.traverse_cfg_tree(_build_header_struct, top)

        if tag_curr[0] == 0:
            hdr_mode = 2
        else:
            hdr_mode = 1

        if re.match('FSP[TMSI]_UPD', path):
            hdr_mode |= 0x80

        # filter out the items to be built for tags and structures
        struct_list = []
        for each in struct_dict:
            match = False
            for check in CGenYamlCfg.exclude_struct:
                if re.match(check, each):
                    match = True
                    if each in tag_dict:
                        if each not in CGenYamlCfg.include_tag:
                            del tag_dict[each]
                    break
            struct_list.append({'name': each, 'alias': '', 'count': 0,
                                'level': struct_dict[each][0],
                                'tag': struct_dict[each][1],
                                'node': struct_dict[each][2],
                                'exclude': True if match else False})

        # sort by level so that the bottom level struct
        # will be build first to satisfy dependencies
        struct_list = sorted(struct_list, key=lambda x: x['level'],
                             reverse=True)

        # Convert XXX_[0-9]+ to XXX as an array hint
        for each in struct_list:
            cfgs = each['node']
            if 'struct' in cfgs['$STRUCT']:
                each['alias'], array_num, var = self.get_struct_array_info(
                    cfgs['$STRUCT']['struct'])
            else:
                match = re.match('(\\w+)(_\\d+)', each['name'])
                if match:
                    each['alias'] = match.group(1)
                else:
                    each['alias'] = each['name']

        # count items for array build
        for idx, each in enumerate(struct_list):
            if idx > 0:
                last_struct = struct_list[idx-1]['node']['$STRUCT']
                curr_struct = each['node']['$STRUCT']
                if struct_list[idx-1]['alias'] == each['alias'] and \
                   curr_struct['length'] == last_struct['length'] and \
                   curr_struct['offset'] == last_struct['offset'] + \
                   last_struct['length']:
                    for idx2 in range(idx-1, -1, -1):
                        if struct_list[idx2]['count'] > 0:
                            struct_list[idx2]['count'] += 1
                            break
                    continue
            each['count'] = 1

        # generate common header
        if com_hdr_file_name:
            self.write_cfg_header_file(com_hdr_file_name, 0, tag_dict,
                                       struct_list)

        # generate platform header
        self.write_cfg_header_file(hdr_file_name, hdr_mode, tag_dict,
                                   struct_list)

        return 0

    def load_yaml(self, cfg_file):
        cfg_yaml = CFG_YAML()
        self.initialize()
        self._cfg_tree = cfg_yaml.load_yaml(cfg_file)
        self._def_dict = cfg_yaml.def_dict
        self.yaml_type = cfg_yaml.yaml_type
        self._yaml_path = os.path.dirname(cfg_file)
        if self.yaml_type == 'vfr':
            self.build_formset_list()
        elif self.yaml_type == 'fsp':
            self.build_cfg_list()
            self.build_var_dict()
            self.update_def_value()
        return 0


def usage():
    print('\n'.join([
          "GenYamlCfg Version 0.50",
          "Usage:",
          "    GenYamlCfg  GENINC  BinFile              IncOutFile "
          "  [-D Macros]",

          "    GenYamlCfg  GENPKL  YamlFile             PklOutFile "
          "  [-D Macros]",
          "    GenYamlCfg  GENBIN  YamlFile[;DltFile]   BinOutFile "
          "  [-D Macros]",
          "    GenYamlCfg  GENDLT  YamlFile[;BinFile]   DltOutFile "
          "  [-D Macros]",
          "    GenYamlCfg  GENYML  YamlFile             YamlOutFile"
          "  [-D Macros]",
          "    GenYamlCfg  GENHDR  YamlFile             HdrOutFile "
          "  [-D Macros]"
          ]))


def main():
    # Parse the options and args
    argc = len(sys.argv)
    if argc < 4:
        usage()
        return 1

    gen_cfg_data = CGenYamlCfg()
    command = sys.argv[1].upper()
    out_file = sys.argv[3]
    if argc >= 5 and gen_cfg_data.parse_macros(sys.argv[4:]) != 0:
        raise Exception("ERROR: Macro parsing failed !")

    file_list = sys.argv[2].split(';')
    if len(file_list) >= 2:
        yml_file = file_list[0]
        dlt_file = file_list[1]
    elif len(file_list) == 1:
        yml_file = file_list[0]
        dlt_file = ''
    else:
        raise Exception("ERROR: Invalid parameter '%s' !" % sys.argv[2])
    yml_scope = ''
    if '@' in yml_file:
        parts = yml_file.split('@')
        yml_file = parts[0]
        yml_scope = parts[1]

    if command == "GENDLT" and yml_file.endswith('.dlt'):
        # It needs to expand an existing DLT file
        dlt_file = yml_file
        lines = gen_cfg_data.expand_include_files(dlt_file)
        write_lines(lines, out_file)
        return 0

    if command == "GENYML":
        if not yml_file.lower().endswith('.yaml'):
            raise Exception('Only YAML file is supported !')
        gen_cfg_data.generate_yml_file(yml_file, out_file)
        return 0

    bin_file = ''
    if (yml_file.lower().endswith('.bin')) and (command == "GENINC"):
        # It is binary file
        bin_file = yml_file
        yml_file = ''

    if bin_file:
        gen_cfg_data.generate_data_inc_file(out_file, bin_file)
        return 0

    cfg_bin_file = ''
    cfg_bin_file2 = ''
    if dlt_file:
        if command == "GENDLT":
            cfg_bin_file = dlt_file
            dlt_file = ''
            if len(file_list) >= 3:
                cfg_bin_file2 = file_list[2]

    if yml_file.lower().endswith('.pkl'):
        with open(yml_file, "rb") as pkl_file:
            gen_cfg_data.__dict__ = marshal.load(pkl_file)
        gen_cfg_data.prepare_marshal(False)

        # Override macro definition again for Pickle file
        if argc >= 5:
            gen_cfg_data.parse_macros(sys.argv[4:])
    else:
        gen_cfg_data.load_yaml(yml_file)
        if command == 'GENPKL':
            gen_cfg_data.prepare_marshal(True)
            with open(out_file, "wb") as pkl_file:
                marshal.dump(gen_cfg_data.__dict__, pkl_file)
            json_file = os.path.splitext(out_file)[0] + '.json'
            fo = open(json_file, 'w')
            path_list = []
            cfgs = {'_cfg_page': gen_cfg_data._cfg_page,
                    '_cfg_list': gen_cfg_data._cfg_list,
                    '_path_list': path_list}
            # optimize to reduce size
            path = None
            for each in cfgs['_cfg_list']:
                new_path = each['path'][:-len(each['cname'])-1]
                if path != new_path:
                    path = new_path
                    each['path'] = path
                    path_list.append(path)
                else:
                    del each['path']
                if each['order'] == each['offset']:
                    del each['order']
                del each['offset']

                # value is just used to indicate display type
                value = each['value']
                if value.startswith('0x'):
                    hex_len = ((each['length'] + 7) // 8) * 2
                    if len(value) == hex_len:
                        value = 'x%d' % hex_len
                    else:
                        value = 'x'
                    each['value'] = value
                elif value and value[0] in ['"', "'", '{']:
                    each['value'] = value[0]
                else:
                    del each['value']

            fo.write(repr(cfgs))
            fo.close()
            return 0

    if dlt_file:
        gen_cfg_data.override_default_value(dlt_file)

    if gen_cfg_data.yaml_type == 'fsp':
        gen_cfg_data.detect_fsp()

    if command == "GENBIN":
        if len(file_list) == 3:
            old_data = gen_cfg_data.generate_binary_array()
            fi = open(file_list[2], 'rb')
            new_data = bytearray(fi.read())
            fi.close()
            if len(new_data) != len(old_data):
                raise Exception("Binary file '%s' length does not match, \
ignored !" % file_list[2])
            else:
                gen_cfg_data.load_default_from_bin(new_data)
                gen_cfg_data.override_default_value(dlt_file)

        gen_cfg_data.generate_binary(out_file, yml_scope)

    elif command == "GENDLT":
        full = True if 'FULL' in gen_cfg_data._macro_dict else False
        gen_cfg_data.generate_delta_file(out_file, cfg_bin_file,
                                         cfg_bin_file2, full)

    elif command == "GENHDR":
        out_files = out_file.split(';')
        brd_out_file = out_files[0].strip()
        if len(out_files) > 1:
            com_out_file = out_files[1].strip()
        else:
            com_out_file = ''
        gen_cfg_data.create_header_file(brd_out_file, com_out_file, yml_scope)

    elif command == "GENINC":
        gen_cfg_data.generate_data_inc_file(out_file)

    elif command == "DEBUG":
        gen_cfg_data.print_cfgs()

    else:
        raise Exception("Unsuported command '%s' !" % command)

    return 0


if __name__ == '__main__':
    sys.exit(main())
